<!--
 * @Author: weidewei
 * @Date: 2025-05-13 09:39:59
 * @LastEditors: weidewei
 * @LastEditTime: 2025-05-13 11:42:41
 * @Description: 
 * @FilePath: \2025-front-face\vue\简化版v2响应式原理.html
-->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>简化版v2响应式原理</title>
  </head>
  <body>
    <script>
      /**
       * Dep 类用于管理依赖关系，通常与 Watcher 实例一起使用。
       * 它负责收集依赖（Watcher 实例）并在需要时通知它们进行更新。
       */
      class Dep {
        constructor() {
          // 初始化一个空数组，用于存储 Watcher 实例
          this.subs = [];
        }

        /**
         * 添加一个 Watcher 实例到依赖列表中。
         * @param {Watcher} sub - 需要添加的 Watcher 实例。
         */
        addSub(sub) {
          this.subs.push(sub);
        }

        /**
         * 通知所有依赖的 Watcher 实例进行更新。
         * 遍历 subs 数组，并调用每个 Watcher 实例的 update 方法。
         */
        notify() {
          this.subs.forEach((sub) => sub.update());
        }
      }

      /**
       * Watcher 类用于观察 Vue 实例中的某个属性或表达式，并在其发生变化时执行回调函数。
       *
       * @param {Object} vm - Vue 实例，Watcher 将观察该实例中的属性或表达式。
       * @param {string|Function} expOrFn - 要观察的属性路径或计算函数，用于获取观察的值。
       * @param {Function} cb - 当观察的值发生变化时执行的回调函数。
       */
      class Watcher {
        constructor(vm, expOrFn, cb) {
          // 将 Vue 实例、回调函数和 getter 函数存储在实例属性中
          this.vm = vm;
          this.cb = cb;
          this.getter = parsePath(expOrFn); // 解析 expOrFn 为 getter 函数
          this.value = this.get(); // 获取初始值并存储在 value 属性中
        }

        /**
         * 获取当前观察的值，并设置当前 Watcher 为 Dep 的目标。
         *
         * @returns {*} - 返回当前观察的值。
         */
        get() {
          Dep.target = this; // 设置当前 Watcher 为 Dep 的目标
          let value = this.getter.call(this.vm, this.vm); // 调用 getter 函数获取值
          Dep.target = null; // 清除 Dep 的目标
          return value; // 返回获取的值
        }

        /**
         * 当观察的值发生变化时，更新 value 并调用回调函数。
         */
        update() {
          const oldValue = this.value; // 保存旧值
          this.value = this.get(); // 获取新值
          this.cb.call(this.vm, this.value, oldValue); // 调用回调函数，传递新值和旧值
        }
      }

      /**
       * 解析路径字符串并返回一个函数，该函数可以根据路径从对象中获取对应的值。
       *
       * @param {string} path - 需要解析的路径字符串，使用"."作为分隔符。
       * @returns {Function} 返回一个函数，该函数接受一个对象作为参数，并返回路径对应的值。
       */
      function parsePath(path) {
        // 将路径字符串按"."分割成多个段
        const segments = path.split(".");
        console.log("segments", segments);

        // 返回一个函数，该函数可以根据路径从对象中获取对应的值
        return function (obj) {
          // 遍历路径的每一段，逐步深入对象
          for (let i = 0; i < segments.length; i++) {
            // 如果对象为null或undefined，则提前返回
            if (!obj) return;
            // 获取当前段对应的值
            obj = obj[segments[i]];
          }
          // 返回最终获取到的值
          return obj;
        };
      }

      /**
       * Observer 类用于观察对象或数组的变化，并将其转换为响应式数据。
       * @class
       * @param {Object|Array} value - 需要被观察的对象或数组。
       */
      class Observer {
        constructor(value) {
          this.value = value;
          this.dep = new Dep(); // 创建一个依赖收集器实例，用于管理依赖关系
          def(value, "__ob__", this); // 将当前 Observer 实例标记到对象的 __ob__ 属性上

          if (Array.isArray(value)) {
            // 如果 value 是数组，则重写数组的原型方法，并递归观察数组中的每个元素
            value.__proto__ = arrayMethods; // 重写数组的原型方法，使其具有响应式能力
            this.observeArray(value);
          } else {
            // 如果 value 是对象，则遍历对象的每个属性，将其转换为响应式属性
            this.walk(value);
          }
        }

        /**
         * 遍历对象的每个属性，并将其转换为响应式属性。
         * @param {Object} obj - 需要被遍历的对象。
         */
        walk(obj) {
          const keys = Object.keys(obj);
          for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i]);
          }
        }

        /**
         * 递归观察数组中的每个元素，将其转换为响应式数据。
         * @param {Array} items - 需要被观察的数组。
         */
        observeArray(items) {
          for (let i = 0, l = items.length; i < l; i++) {
            observe(items[i]);
          }
        }
      }

      /**
       * 定义一个响应式属性，使得该属性在被访问或修改时能够触发依赖收集和更新。
       *
       * @param {Object} obj - 需要定义响应式属性的对象。
       * @param {string} key - 需要定义为响应式属性的键名。
       * @returns {void} 该函数没有返回值。
       */
      function defineReactive(obj, key) {
        // 创建一个依赖收集器实例，用于管理该属性的依赖
        const dep = new Dep();
        // 获取对象当前属性的值
        let val = obj[key];

        // 递归观察子对象，如果子对象是对象或数组，则将其也转换为响应式
        let childOb = observe(val);

        // 使用 Object.defineProperty 定义或修改对象的属性
        Object.defineProperty(obj, key, {
          enumerable: true, // 该属性可枚举
          configurable: true, // 该属性可配置
          get: function reactiveGetter() {
            // 如果当前有依赖目标（如 Watcher），则将其添加到依赖列表中
            if (Dep.target) {
              dep.addSub(Dep.target);
              // 如果子对象也是响应式的，则将其依赖也添加到子对象的依赖列表中
              if (childOb) {
                childOb.dep.addSub(Dep.target);
              }
            }
            // 返回当前属性的值
            return val;
          },
          set: function reactiveSetter(newVal) {
            // 如果新值与旧值相同，则不进行任何操作
            if (newVal === val) return;
            // 更新属性的值
            val = newVal;
            // 对新值进行递归观察，确保新值也是响应式的
            childOb = observe(newVal);
            // 通知所有依赖该属性的 Watcher 进行更新
            dep.notify();
          },
        });
      }

      /**
       * 观察一个对象，使其变为响应式对象。
       * 如果对象已经是响应式的，则直接返回其观察者实例；
       * 否则，创建一个新的观察者实例并返回。
       *
       * @param {*} value - 需要被观察的值。如果该值不是对象或为null，则直接返回。
       * @returns {Observer|undefined} - 返回一个观察者实例，如果传入的值不是对象或为null，则返回undefined。
       */
      function observe(value) {
        // 如果传入的值不是对象或为null，则直接返回
        if (typeof value !== "object" || value === null) {
          return;
        }

        let ob;
        // 如果对象已经有观察者实例，则直接使用该实例
        if (value.__ob__ && value.__ob__ instanceof Observer) {
          ob = value.__ob__;
        } else {
          // 否则，创建一个新的观察者实例
          ob = new Observer(value);
        }

        return ob;
      }

      // 定义数组原型和变异方法对象
      const arrayProto = Array.prototype; // 获取原生数组原型
      const arrayMethods = Object.create(arrayProto); // 创建基于原生原型的空对象
      // 需要重写的数组方法列表
      const methodsToPatch = [
        "push",
        "pop",
        "shift",
        "unshift",
        "splice",
        "sort",
        "reverse",
      ];

      /**
       * 重写数组原型方法，使其能够触发响应式更新
       *
       * 实现思路：
       * 1. 保留原始数组方法引用
       * 2. 创建包装方法，在调用原始方法后执行额外操作
       * 3. 检测可能新增的数组元素并进行响应式处理
       * 4. 手动触发依赖通知
       */
      methodsToPatch.forEach(function (method) {
        // 保存原始数组方法引用
        const original = arrayProto[method];

        // 定义新的变异方法
        def(arrayMethods, method, function mutator(...args) {
          // 1. 调用原始数组方法获取结果
          const result = original.apply(this, args);

          // 2. 获取数组关联的 Observer 实例
          const ob = this.__ob__;

          // 3. 处理新增元素（仅对会添加元素的方法进行处理）
          let inserted;
          switch (method) {
            case "push":
            case "unshift":
              // push/unshift 添加的参数即为新增元素
              inserted = args;
              break;
            case "splice":
              // splice 方法的第三个参数开始是新增元素
              inserted = args.slice(2);
              break;
          }

          // 4. 对新增元素进行响应式处理
          if (inserted) {
            // 递归观察新增元素（如果是对象/数组）
            ob.observeArray(inserted);
          }

          // 5. 通知依赖更新（关键步骤）
          // 通过关联的 Observer 实例的 dep 属性通知所有 Watcher
          ob.dep.notify();

          // 6. 返回原始方法执行结果
          return result;
        });
      });
      /**
       * 定义或修改对象属性（支持深度响应式处理）
       * @param {Object} obj - 目标对象
       * @param {string} key - 要操作的属性名
       * @param {*} val - 属性值
       * @param {boolean} [enumerable=false] - 是否可枚举
       * @returns {void}
       * @example
       * const obj = {};
       * def(obj, 'prop', 42, true);
       */
      function def(obj, key, val, enumerable) {
        // 使用属性描述符定义对象属性
        Object.defineProperty(obj, key, {
          value: val, // 设置属性初始值
          enumerable: !!enumerable, // 强制转换为布尔值，决定是否可枚举
          writable: true, // 允许后续通过赋值运算符修改值
          configurable: true, // 允许后续重新定义属性描述符
        });
      }

      /**
       * 安全判断对象自身属性（非原型链继承）
       * @param {Object} obj - 需要检查的目标对象
       * @param {string|symbol} key - 需要检查的属性键
       * @returns {boolean} 是否是对象自身属性
       * @example
       * hasOwn({a:1}, 'a') // => true
       * hasOwn({}, 'toString') // => false
       */
      function hasOwn(obj, key) {
        return Object.prototype.hasOwnProperty.call(obj, key);
      }

      /**
       * Vue 核心类简化实现（响应式系统）
       * @class
       */
      class Vue {
        /**
         * 构造函数
         * @param {Object} options - 配置选项
         * @param {Object} options.data - 组件数据对象
         */
        constructor(options) {
          /**
           * 原始数据存储
           * @private
           */
          this._data = options.data;

          // 将数据属性代理到 Vue 实例上
          this._proxyData(this._data);

          // 创建响应式观察者
          new Observer(this._data);
        }

        /**
         * 数据代理方法（实现 this.xxx 访问 this._data.xxx）
         * @private
         * @param {Object} data - 需要代理的数据对象
         * @description
         * 通过 Object.defineProperty 实现属性访问代理
         * 使得可以直接通过实例属性访问数据（this.a 代替 this._data.a）
         */
        _proxyData(data) {
          Object.keys(data).forEach((key) => {
            Object.defineProperty(this, key, {
              /**
               * 属性访问代理
               * @returns {*} 对应数据值
               */
              get() {
                return this._data[key];
              },

              /**
               * 属性修改代理
               * @param {*} newVal - 新设置的值
               */
              set(newVal) {
                this._data[key] = newVal;
              },
            });
          });
        }

        /**
         * 观察数据变化方法
         * @param {string|Function} expOrFn - 要观察的表达式或函数
         * @param {Function} cb - 变化时的回调函数
         * @description
         * 创建 Watcher 实例来实现：
         * 1. 初始化时收集依赖
         * 2. 数据变化时触发回调
         * @example
         * vm.$watch('a.b.c', (newVal, oldVal) => { ... })
         */
        $watch(expOrFn, cb) {
          new Watcher(this, expOrFn, cb);
        }
      }

      const vm = new Vue({
        data: {
          message: "Hello",
          user: {
            name: "John",
            age: 30,
          },
          list: [1, 2, 3],
        },
      });

      vm.$watch("message", function (newVal, oldVal) {
        console.log(`message改变了，旧值： ${oldVal}，新值： ${newVal}`);
      });

      vm.$watch("user.name", function (newVal, oldVal) {
        console.log(`name改变了，旧值 ：${oldVal}，新值：${newVal}`);
      });

      vm.message = "Hello World"; // 触发更新
      vm.user.name = "Doe"; // 触发更新
      vm.list.push(4); // 触发数组更新
    </script>
  </body>
</html>
